#include "Node/LRG_Node_GridSpatial2D.h"

#include "BlueprintFunctionLibrary/LineReplicationGraphFunctions.h"
#include "Component/LineReplicationGraphComponent.h"
#include "Component/LRG_ViewerComponent.h"
#include "Net/NetworkGranularMemoryLogging.h"

TMap<int32, ULRG_Node_GridCell*> ULRG_Node_GridSpatial::GetGridNodesForActorByCellInfo(const ULRG_Node_GridSpatial::FActorCellInfo& CellInfo)
{
	const int32 StartX = CellInfo.StartX;
	const int32 StartY = CellInfo.StartY;
	const int32 EndX = CellInfo.EndX;
	const int32 EndY = CellInfo.EndY;

	TMap<int32, ULRG_Node_GridCell*> Result;
	for (int32 X = StartX; X <= EndX; X++)
	{
		for (int32 Y = StartY; Y <= EndY; Y++)
		{
			if(ULRG_Node_GridCell* NodePtr = GetCell(X, Y))
			{
				Result.Add(NodePtr->GetCellId(), NodePtr);;
			}
		}
	}
	return Result;
}

void ULRG_Node_GridSpatial::PrepareForReplication()
{
	bool bDebugCellChange = false;
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
	PrepareForReplicationCount += 1;
	int PrintCellCountInternal = 0;
#else
	int PrintCellCountInternal = 0;
	int PrintCellCountInternal = 0;
#endif

#if	WITH_SERVER_CODE
	RG_QUICK_SCOPE_CYCLE_COUNTER(ULRG_Node_GridSpatial_PrepareForReplication);

	FGlobalActorReplicationInfoMap* GlobalRepMap = GraphGlobals.IsValid() ? GraphGlobals->GlobalActorReplicationInfoMap : nullptr;

	TMap<int64, int> GridActorCountMap;
	for (ULineReplicationGraphComponent* Component : DynamicActorComponentSet)
	{
		RG_QUICK_SCOPE_CYCLE_COUNTER(ULRG_Node_GridSpatial_DynamicSpatializedActors)
		AActor* DynamicActor = Component->GetOwner();
		FNewReplicatedActorInfo& ActorInfo = Component->GetNewReplicatedActorInfo();

#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
		if (!IsActorValidForReplicationGather(DynamicActor))
		{
			UE_LOG(LogReplicationGraph, Warning, TEXT("ULRG_Node_GridSpatial::PrepareForReplication: Dynamic Actor no longer ready for replication"));
			UE_LOG(LogReplicationGraph, Warning, TEXT("%s"), *GetNameSafe(DynamicActor));
			continue;
		}
#endif

		// Update location
		FGlobalActorReplicationInfo& ActorRepInfo = GlobalRepMap->Get(DynamicActor);

		// Check if this resets spatial bias
		const FVector Location3D = DynamicActor->GetActorLocation();
		ActorRepInfo.WorldLocation = Location3D;
		Component->SetWorldLocation(Location3D);
		
		// Get the new CellInfo
		const FActorCellInfo NewCellInfo = GetCellInfoForActor(DynamicActor, Location3D, Component->GetClassReplicationInfo().GetCullDistance());
		UpdateLocationCell(Component);
		if (PrintCellCountInternal && PrepareForReplicationCount > PrintCellCountInternal)
		{
			int64 ActorLocationGridKey = LocationToId(Location3D);
			auto& Count = GridActorCountMap.FindOrAdd(ActorLocationGridKey);
			Count += 1;
		}
		const TMap<int32, ULRG_Node_GridCell*>& TempPreviousNodes = Component->GetInfluenceCells();
		
		TMap<int32, ULRG_Node_GridCell*> TempNewNodes = GetGridNodesForActorByCellInfo(NewCellInfo);

		for(const auto& KeyValue : TempPreviousNodes)
		{
			if(!TempNewNodes.Contains(KeyValue.Key))
			{
				//KeyValue.Value->RemoveDynamicActor(ActorInfo);
				KeyValue.Value->RemoveActor_Dynamic(Component);
			}
		}
		for(const auto& KeyValue : TempNewNodes)
		{
			if(!TempPreviousNodes.Contains(KeyValue.Key))
			{
				//KeyValue.Value->AddDynamicActor(ActorInfo);
				KeyValue.Value->AddActor_Dynamic(Component);
			}
		}
		//if(bDebugCellChange)
		//{
		//	DebugCellChange(DynamicActor, TempPreviousNodes, TempNewNodes);
		//}
		Component->SetInfluenceCells(TempNewNodes);
	}
	
	// -------------------------------------------
	//	Pending Spatial Actors
	// -------------------------------------------
	for (int32 idx=PendingStaticSpatializedActors.Num()-1; idx>=0; --idx)
	{
		FPendingStaticActors& PendingStaticActor = PendingStaticSpatializedActors[idx];
		if (PendingStaticActor.Actor->IsActorInitialized() == false)
		{
			continue;
		}

		FNewReplicatedActorInfo NewActorInfo(PendingStaticActor.Actor);
		FGlobalActorReplicationInfo& GlobalInfo = GraphGlobals->GlobalActorReplicationInfoMap->Get(PendingStaticActor.Actor);

		AddActorInternal_Static_Implementation(NewActorInfo, GlobalInfo, PendingStaticActor.DormancyDriven);

		PendingStaticSpatializedActors.RemoveAtSwap(idx, 1, false);
	}
	
	if(PrintCellCountInternal && PrepareForReplicationCount > PrintCellCountInternal)
	{
		PrepareForReplicationCount = 0;
		FString CellCountInfo;
		GridActorCountMap.ValueSort([](const int& Key1, const int& Key2)
		{
			return Key1 > Key2;
		});
		int Num = 8;
		for(const auto& KeyValue : GridActorCountMap)
		{
			if(Num < 0)
			{
				break;
			}
			Num -= 1;
			int X, Y = 0;
			CalcGridCellIndex(KeyValue.Key, X, Y);
			CellCountInfo += FString::Printf(TEXT("[%d,%d]=%d."), X, Y, KeyValue.Value);
		}
		UE_LOG(LogReplicationGraph, Log, TEXT("%s"), *CellCountInfo);
	}
#endif // WITH_SERVER_CODE
}

struct FGPlayerGridCellInformation
{
	//FIntPoint CurLocation;
	//FIntPoint PrevLocation;

	FSpatialCoordinate CurrentCoordinate;
	FSpatialCoordinate PrevCoordinate;
};

constexpr double RepGraphWorldMax = UE_OLD_WORLD_MAX;
constexpr double RepGraphHalfWorldMax = UE_OLD_HALF_WORLD_MAX;

int32 RepGraph_GridSpatialization2D_DestroyDormantDynamicActorsDefault = 1;
int32 RepGraph_DormantDynamicActorsDestruction = 0;
int32 RepGraph_ReplicatedDormantDestructionInfosPerFrame = MAX_int32;
int RepGraph_Verify = 0;

ULRG_Node_GridSpatial::ULRG_Node_GridSpatial()
	: CellSize(0.f)
	, ConnectionMaxZ(RepGraphWorldMax)
{
	bRequiresPrepareForReplicationCall = true;
	bDestroyDormantDynamicActors = RepGraph_GridSpatialization2D_DestroyDormantDynamicActorsDefault != 0;
}

void ULRG_Node_GridSpatial::Serialize(FArchive& Ar)
{
	Super::Serialize(Ar);

	if (Ar.IsCountingMemory())
	{
		GRANULAR_NETWORK_MEMORY_TRACKING_INIT(Ar, "ULRG_Node_GridSpatial::Serialize");
		
		GRANULAR_NETWORK_MEMORY_TRACKING_TRACK("DynamicSpatializedActors", DynamicActorComponentSet.CountBytes(Ar));
		GRANULAR_NETWORK_MEMORY_TRACKING_TRACK("StaticSpatializedActors", StaticSpatializedActors.CountBytes(Ar));
		GRANULAR_NETWORK_MEMORY_TRACKING_TRACK("PendingStaticSpatializedActors", PendingStaticSpatializedActors.CountBytes(Ar));
	}
}

void ULRG_Node_GridSpatial::NotifyAddNetworkActor(const FNewReplicatedActorInfo& ActorInfo)
{
	ensureAlwaysMsgf(false, TEXT("ULRG_Node_GridSpatial::NotifyAddNetworkActor should not be called directly"));
}

bool ULRG_Node_GridSpatial::NotifyRemoveNetworkActor(const FNewReplicatedActorInfo& ActorInfo, bool bWarnIfNotFound)
{
	ensureAlwaysMsgf(false, TEXT("ULRG_Node_GridSpatial::NotifyRemoveNetworkActor should not be called directly"));
	return false;
}

void ULRG_Node_GridSpatial::AddActor_Dormancy(const FNewReplicatedActorInfo& ActorInfo, FGlobalActorReplicationInfo& ActorRepInfo)
{
	//if(ReplicationGraphDebugActor)
	//{
	//	UE_LOG(LogReplicationGraph, Display, TEXT("ULRG_Node_GridSpatial::AddActor_Dormancy %s on %s"), *ActorInfo.Actor->GetFullName(), *GetPathName());
	//}

	if (ActorRepInfo.bWantsToBeDormant)
	{
		AddActorInternal_Static(ActorInfo, ActorRepInfo, true);
	}
	else
	{
		AddActorInternal_Dynamic(ActorInfo);
	}

	// Tell us if dormancy changes for this actor because then we need to move it. Note we don't care about Flushing.
	ActorRepInfo.Events.DormancyChange.AddUObject(this, &ULRG_Node_GridSpatial::OnNetDormancyChange);
}

void ULRG_Node_GridSpatial::RemoveActor_Static(const FNewReplicatedActorInfo& ActorInfo)
{
	//UE_CLOG(ReplicationGraphDebugActor, LogReplicationGraph, Display, TEXT("ULRG_Node_GridSpatial::RemoveActor_Static %s on %s"), *ActorInfo.Actor->GetFullName(), *GetPathName());

	if (GraphGlobals.IsValid())
	{
		FGlobalActorReplicationInfo& GlobalInfo = GraphGlobals->GlobalActorReplicationInfoMap->Get(ActorInfo.Actor);
		RemoveActorInternal_Static(ActorInfo, GlobalInfo, GlobalInfo.bWantsToBeDormant); 
	}
}

void ULRG_Node_GridSpatial::RemoveActor_Dormancy(const FNewReplicatedActorInfo& ActorInfo)
{
	//UE_CLOG(ReplicationGraphDebugActor, LogReplicationGraph, Display, TEXT("ULRG_Node_GridSpatial::RemoveActor_Dormancy %s on %s"), *ActorInfo.Actor->GetFullName(), *GetPathName());

	if (GraphGlobals.IsValid())
	{
		FGlobalActorReplicationInfo& ActorRepInfo = GraphGlobals->GlobalActorReplicationInfoMap->Get(ActorInfo.Actor);
		if (ActorRepInfo.bWantsToBeDormant)
		{
			RemoveActorInternal_Static(ActorInfo, ActorRepInfo, true);
		}
		else
		{
			RemoveActorInternal_Dynamic(ULineReplicationGraphComponent::Get( ActorInfo.Actor) );
		}

		// AddActorInternal_Static and AddActorInternal_Dynamic will both override Actor information if they are called repeatedly.
		// This means that even if AddActor_Dormancy is called multiple times with the same Actor, a single call to RemoveActor_Dormancy
		// will completely remove the Actor from either the Static or Dynamic list appropriately.
		// Therefore, it should be safe to call RemoveAll and not worry about trying to track individual delegate handles.
		ActorRepInfo.Events.DormancyChange.RemoveAll(this);
	}
}

void ULRG_Node_GridSpatial::AddActor_Dynamic(ULineReplicationGraphComponent* Component)
{
	DynamicActorComponentSet.Emplace(Component);
}

void ULRG_Node_GridSpatial::AddActor_Dormancy(ULineReplicationGraphComponent* Component)
{
	
}

void ULRG_Node_GridSpatial::AddActorInternal_Dynamic(const FNewReplicatedActorInfo& ActorInfo)
{
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
	if (ActorInfo.Actor->bAlwaysRelevant)
	{
		UE_LOG(LogReplicationGraph, Warning, TEXT("Always relevant actor being added to spatialized graph node. %s"), *GetNameSafe(ActorInfo.Actor));
		return;
	}
#endif

	//UE_CLOG(ReplicationGraphDebugActor, LogReplicationGraph, Display, TEXT("UReplicationGraph::AddActorInternal_Dynamic %s"), *ActorInfo.Actor->GetFullName());

	//DynamicSpatializedActors.Emplace(ActorInfo.Actor, ActorInfo);
}

void ULRG_Node_GridSpatial::AddActor_Static(ULineReplicationGraphComponent* Component, FGlobalActorReplicationInfo& ActorRepInfo)
{
	AActor* Actor = Component->GetOwner();
}

void ULRG_Node_GridSpatial::AddActor_Static_Internal(ULineReplicationGraphComponent* Component, FGlobalActorReplicationInfo& ActorRepInfo, bool IsDormancyDriven)
{
	
}

void ULRG_Node_GridSpatial::AddActorInternal_Static(const FNewReplicatedActorInfo& ActorInfo, FGlobalActorReplicationInfo& ActorRepInfo, bool bDormancyDriven)
{
	AActor* Actor = ActorInfo.Actor;
	if (Actor->IsActorInitialized() == false)
	{
		// Make sure its not already in the list. This should really not happen but would be very bad if it did. This list should always be small so doing the safety check seems good.
		for (int32 idx=PendingStaticSpatializedActors.Num()-1; idx >= 0; --idx)
		{
			if (PendingStaticSpatializedActors[idx].Actor == ActorInfo.Actor)
			{
				UE_LOG(LogReplicationGraph, Warning, TEXT("ULRG_Node_GridSpatial::AddActorInternal_Static was called on %s when it was already in the PendingStaticSpatializedActors list!"), *Actor->GetPathName());
				return;
			}
		}

		PendingStaticSpatializedActors.Emplace(ActorInfo.Actor, bDormancyDriven);
		return;
	}

	if (RepGraph_Verify)
	{
		ensureMsgf(PendingStaticSpatializedActors.Contains(ActorInfo.Actor) == false, TEXT("ULRG_Node_GridSpatial::AddActorInternal_Static was called on %s when it was already in the PendingStaticSpatializedActors list!"), *Actor->GetPathName());
	}

	AddActorInternal_Static_Implementation(ActorInfo, ActorRepInfo, bDormancyDriven);
}

void ULRG_Node_GridSpatial::AddActorInternal_Static_Implementation(const FNewReplicatedActorInfo& ActorInfo, FGlobalActorReplicationInfo& ActorRepInfo, bool bDormancyDriven)
{
	AActor* Actor = ActorInfo.Actor;
	const FVector Location3D = Actor->GetActorLocation();
	ActorRepInfo.WorldLocation = Location3D;
	
	StaticSpatializedActors.Emplace(Actor, FCachedStaticActorInfo(ActorInfo, bDormancyDriven));
	
	PutStaticActorIntoCell(ActorInfo, ActorRepInfo, bDormancyDriven);
}

void ULRG_Node_GridSpatial::RemoveActorInternal_Dynamic(ULineReplicationGraphComponent* Component)
{
	for(const auto& KeyValue : Component->GetInfluenceCells())
	{
		if(auto* GridCell = KeyValue.Value)
		{
			GridCell->RemoveActor_Dynamic(Component);
		}
	}
	DynamicActorComponentSet.Remove(Component);
}

void ULRG_Node_GridSpatial::RemoveActorInternal_Static(const FNewReplicatedActorInfo& ActorInfo, FGlobalActorReplicationInfo& ActorRepInfo, bool bWasAddedAsDormantActor)
{
	if (StaticSpatializedActors.Remove(ActorInfo.Actor) <= 0)
	{
		// May have been a pending actor
		for (int32 idx=PendingStaticSpatializedActors.Num()-1; idx >= 0; --idx)
		{
			if (PendingStaticSpatializedActors[idx].Actor == ActorInfo.Actor)
			{
				PendingStaticSpatializedActors.RemoveAtSwap(idx, 1, false);
				return;
			}
		}

		UE_LOG(LogReplicationGraph, Warning, TEXT("UReplicationGraphNode_Simple2DSpatialization::RemoveActorInternal_Static attempted remove %s from static list but it was not there."), *GetActorRepListTypeDebugString(ActorInfo.Actor));
		if(DynamicActorComponentSet.Remove(nullptr) > 0)	//TODO
		{
			UE_LOG(LogReplicationGraph, Warning, TEXT("   It was in DynamicStreamingSpatializedActors!"));
		}
	}

	// Remove it from the actual node it should still be in. Note that even if the actor did move in between this and the last replication frame, the FGlobalActorReplicationInfo would not have been updated
	static TArray<ULRG_Node_GridCell*> s_GatheredNodes;
	s_GatheredNodes.Empty(s_GatheredNodes.Max());
	GetGridNodesForActor(ActorInfo.Actor, ActorRepInfo, s_GatheredNodes);
	for (auto* Node : s_GatheredNodes)
	{
		Node->RemoveStaticActor(ActorInfo, ActorRepInfo, bWasAddedAsDormantActor);
	}

	if (RepGraph_Verify)
	{
		// Verify this actor is in no nodes. This is pretty slow!
		TArray<AActor*> AllActors;
		for(const auto& KeyValue : GridCellMap)
		{
			ULRG_Node_GridCell* GridCell = KeyValue.Value;
			if(GridCell)
			{
				AllActors.Reset();
				GridCell->GetAllActorsInNode_Debugging(AllActors);
					
				ensureMsgf(AllActors.Contains(ActorInfo.Actor) == false, TEXT("Actor still in a node after removal!. %s. Removal Location: %s"), *GridCell->GetDesc(), *ActorRepInfo.WorldLocation.ToString());
			}
		}
	}
}

void ULRG_Node_GridSpatial::OnNetDormancyChange(FActorRepListType Actor, FGlobalActorReplicationInfo& GlobalInfo, ENetDormancy NewValue, ENetDormancy OldValue)
{
	const bool bCurrentShouldBeStatic = NewValue > DORM_Awake;
	const bool bPreviousShouldBeStatic = OldValue > DORM_Awake;

	if (bCurrentShouldBeStatic && !bPreviousShouldBeStatic)
	{
		// Actor was dynamic and is now static. Remove from dynamic list and add to static.
		FNewReplicatedActorInfo ActorInfo(Actor);
		RemoveActorInternal_Dynamic(ULineReplicationGraphComponent::Get(Actor));
		AddActorInternal_Static(ActorInfo, GlobalInfo, true);
	}
	else if (!bCurrentShouldBeStatic && bPreviousShouldBeStatic)
	{
		FNewReplicatedActorInfo ActorInfo(Actor);
		RemoveActorInternal_Static(ActorInfo, GlobalInfo, true); // This is why we need the 3rd bool parameter: this actor was placed as dormant (and it no longer is at the moment of this callback)
		AddActorInternal_Dynamic(ActorInfo);
	}
}

void ULRG_Node_GridSpatial::NotifyResetAllNetworkActors()
{
	StaticSpatializedActors.Reset();
	DynamicActorComponentSet.Reset();
	PendingStaticSpatializedActors.Reset();
	Super::NotifyResetAllNetworkActors();
}

void ULRG_Node_GridSpatial::PutStaticActorIntoCell(const FNewReplicatedActorInfo& ActorInfo, FGlobalActorReplicationInfo& ActorRepInfo, bool bDormancyDriven)
{
	CalculateActorLocation(ActorRepInfo.WorldLocation);
	static TArray<ULRG_Node_GridCell*> s_GatheredNodes;
    s_GatheredNodes.Empty(s_GatheredNodes.Max());
	GetGridNodesForActor(ActorInfo.Actor, ActorRepInfo, s_GatheredNodes);
	for (auto* Node : s_GatheredNodes)
	{
		Node->AddStaticActor(ActorInfo, ActorRepInfo, bDormancyDriven);
	}
}

void ULRG_Node_GridSpatial::GetGridNodesForActor(FActorRepListType Actor, const FGlobalActorReplicationInfo& ActorRepInfo, TArray<ULRG_Node_GridCell*>& OutNodes)
{
	RG_QUICK_SCOPE_CYCLE_COUNTER(ULRG_Node_GridSpatial_GetGridNodesForActor);
	GetGridNodesForActor(Actor, GetCellInfoForActor(Actor, ActorRepInfo.WorldLocation, ActorRepInfo.Settings.GetCullDistance()), OutNodes);
}

ULRG_Node_GridSpatial::FActorCellInfo ULRG_Node_GridSpatial::GetCellInfoForActor(FActorRepListType Actor, const FVector& Location3D, float CullDistance)
{
	using namespace UE::Net::Private;

#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
	if (CullDistance <= 0.f)
	{
		UE_LOG(LogReplicationGraph, Warning, TEXT("ULRG_Node_GridSpatial::GetCellInfoForActor called on %s when its CullDistance = %.2f. (Must be > 0)"), *GetActorRepListTypeDebugString(Actor), CullDistance);
	}
#endif

	FVector ClampedLocation = Location3D;

	// Sanity check the actor's location. If it's garbage, we could end up with a gigantic allocation in GetGridNodesForActor as we adjust the grid.
	if (Location3D.X < -RepGraphHalfWorldMax || Location3D.X > RepGraphHalfWorldMax ||
		Location3D.Y < -RepGraphHalfWorldMax || Location3D.Y > RepGraphHalfWorldMax ||
		Location3D.Z < -RepGraphHalfWorldMax || Location3D.Z > RepGraphHalfWorldMax)
	{
		UE_LOG(LogReplicationGraph, Warning, TEXT("ULRG_Node_GridSpatial::GetCellInfoForActor: Actor %s is outside world bounds with a location of %s. Clamping grid location to world bounds."), *GetFullNameSafe(Actor), *Location3D.ToString());
		ClampedLocation = Location3D.BoundToCube(RepGraphHalfWorldMax);
	}

	if (!ensureMsgf(!ClampedLocation.ContainsNaN(), TEXT("ULRG_Node_GridSpatial::GetCellInfoForActor: Actor %s has an invalid location of %s, defaulting to the origin."), *GetFullNameSafe(Actor), *ClampedLocation.ToString()))
	{
		ClampedLocation = FVector::ZeroVector;
	}

	FActorCellInfo CellInfo;
	const auto LocationBiasX = (ClampedLocation.X);
	const auto LocationBiasY = (ClampedLocation.Y);

	const auto Dist = CullDistance;
	const auto MinX = LocationBiasX - Dist;
	const auto MinY = LocationBiasY - Dist;
	auto MaxX = LocationBiasX + Dist;
	auto MaxY = LocationBiasY + Dist;

	CellInfo.StartX = CalculateCoordinate(MinX);
	CellInfo.StartY = CalculateCoordinate(MinY);

	CellInfo.EndX = CalculateCoordinate(MaxX);
	CellInfo.EndY = CalculateCoordinate(MaxY);
	return CellInfo;
}

void ULRG_Node_GridSpatial::GetGridNodesForActor(FActorRepListType Actor, const ULRG_Node_GridSpatial::FActorCellInfo& CellInfo, TArray<ULRG_Node_GridCell*>& OutNodes)
{
	if (!ensure(CellInfo.IsValid()))
	{
		return;
	}

	OutNodes.Reset();

	const int32 StartX = CellInfo.StartX;
	const int32 StartY = CellInfo.StartY;
	const int32 EndX = CellInfo.EndX;
	const int32 EndY = CellInfo.EndY;
	
	for (int32 X = StartX; X <= EndX; X++)
	{
		for (int32 Y = StartY; Y <= EndY; Y++)
		{
			if(auto* GridCell = GetCell(X, Y))
			{
				OutNodes.Add(GridCell);
			}
		}
	}
}

void ULRG_Node_GridSpatial::GatherActorListsForConnection(const FConnectionGatherActorListParameters& Params)
{
	using namespace UE::Net::Private;
	TRACE_CPUPROFILER_EVENT_SCOPE(GridSpatial_GatherActorListsForConnection)
#if WITH_SERVER_CODE
	TArray<FLastLocationGatherInfo>& LastLocationArray = Params.ConnectionManager.LastGatherLocations;
	TSet<int32> UniqueCurrentCoordinateIdSet;

	// Consider all users that are in cells for this connection. 
	// From here, generate a list of coordinates, we'll later work through each coordinate pairing
	// to find the cells that are actually active. This reduces redundancy and cache misses.
	TArray<FGPlayerGridCellInformation, FReplicationGraphConnectionsAllocator> ActiveGridCells;
	for (const FNetViewer& CurViewer : Params.Viewers)
	{
		FVector ClampedViewLoc = CurViewer.ViewLocation;
		if (CurViewer.ViewTarget)
		{
			ClampedViewLoc = CurViewer.ViewTarget->GetActorLocation();
		}
		if (ClampedViewLoc.Z > ConnectionMaxZ)
		{
			continue;
		}
		ClampedViewLoc = ClampedViewLoc.BoundToCube(UE_OLD_HALF_WORLD_MAX);

		FSpatialCoordinate Coordinate = LocationToCoordinate(ClampedViewLoc);
		FGPlayerGridCellInformation NewPlayerCell;
		NewPlayerCell.CurrentCoordinate = Coordinate;

		FLastLocationGatherInfo* GatherInfoForConnection = nullptr;

		// Save this information out for later.
		if (CurViewer.Connection != nullptr)
		{
			GatherInfoForConnection = LastLocationArray.FindByKey<UNetConnection*>(CurViewer.Connection);

			// Add any missing last location information that we don't have
			if (GatherInfoForConnection == nullptr)
			{
				GatherInfoForConnection = &LastLocationArray[LastLocationArray.Emplace(CurViewer.Connection, FVector(ForceInitToZero))];
			}
		}

		FVector LastLocationForConnection = GatherInfoForConnection ? GatherInfoForConnection->LastLocation : ClampedViewLoc;
		LastLocationForConnection = LastLocationForConnection.BoundToCube(UE_OLD_HALF_WORLD_MAX);

		// Try to determine the previous location of the user.
		NewPlayerCell.PrevCoordinate = LocationToCoordinate(LastLocationForConnection);

		// If we have not operated on this cell yet (meaning it's not shared by anyone else), gather for it.
		if (!UniqueCurrentCoordinateIdSet.Contains(NewPlayerCell.CurrentCoordinate.ToId()))
		{
			if (ULRG_Node_GridCell* GridCell = GetCell(Coordinate.X, Coordinate.Y))
			{
				GridCell->GatherActorListsForConnection(Params);
			}

			UniqueCurrentCoordinateIdSet.Add(NewPlayerCell.CurrentCoordinate.ToId());
		}

		// Add this to things we consider later.
		ActiveGridCells.Add(NewPlayerCell);
	}

	if (bDestroyDormantDynamicActors && RepGraph_DormantDynamicActorsDestruction > 0)
	{
		FActorRepListRefView& PrevDormantActorList = Params.ConnectionManager.GetPrevDormantActorListForNode(this);

		// Process and create the dormancy list for the active grid for this user
		for (const FGPlayerGridCellInformation& CellInfo : ActiveGridCells)
		{
			const int32& CellX = CellInfo.CurrentCoordinate.X;
			const int32& CellY = CellInfo.CurrentCoordinate.Y;
			const int32& PrevX = CellInfo.PrevCoordinate.X;
			const int32& PrevY = CellInfo.PrevCoordinate.Y;

			// The idea is that if the previous location is a current location for any other user, we do not bother to do operations on this cell
			// However, if the current location matches with a current location of another user, continue anyways.
			//
			// as above, if the grid cell changed this gather and is not in current use by any other viewer

			// TODO: There is a potential list gathering redundancy if two actors share the same current and previous cell information
			// but this should just result in a wasted cycle if anything.
			if (((CellX != PrevX) || (CellY != PrevY)) && !UniqueCurrentCoordinateIdSet.Contains(CellInfo.PrevCoordinate.ToId()))
			{
				RG_QUICK_SCOPE_CYCLE_COUNTER(ULRG_Node_GridSpatial_CellChangeDormantRelevancy);
				FActorRepListRefView DormantActorList;
				
				if (auto* CellNode = GetCell(CellX, CellY))
				{
					if (UReplicationGraphNode_DormancyNode* DormancyNode = CellNode->GetDormancyNode())
					{
						// Making sure to remove from the PrevDormantActorList since we don't want things added to the DormantActorList to be destroyed anymore
						DormancyNode->ConditionalGatherDormantDynamicActors(DormantActorList, Params, nullptr, false, &PrevDormantActorList);
					}
				}

				// Determine dormant actors for our last location. Do not add actors if they are relevant to anyone.
				if (auto* PrevCell = GetCell(PrevX, PrevY))
				{
					if (UReplicationGraphNode_DormancyNode* DormancyNode = PrevCell->GetDormancyNode())
					{
						DormancyNode->ConditionalGatherDormantDynamicActors(PrevDormantActorList, Params, &DormantActorList, true);
					}
				}
			}
		}

		if (PrevDormantActorList.Num() > 0)
		{
			int32 NumActorsToRemove = RepGraph_ReplicatedDormantDestructionInfosPerFrame;

			UE_LOG(LogReplicationGraph, Verbose, TEXT("ULRG_Node_GridSpatial::GatherActorListsForConnection: Removing %d Actors (List size: %d)"), FMath::Min(NumActorsToRemove, PrevDormantActorList.Num()), PrevDormantActorList.Num());

			FGlobalActorReplicationInfoMap* GlobalRepMap = GraphGlobals.IsValid() ? GraphGlobals->GlobalActorReplicationInfoMap : nullptr;

			// any previous dormant actors not in the current node dormant list
			for (int32 i = 0; i < PrevDormantActorList.Num() && NumActorsToRemove > 0; i++)
			{
				FActorRepListType& Actor = PrevDormantActorList[i];

				const FGlobalActorReplicationInfo* GlobalActorInfo = GlobalRepMap != nullptr ? GlobalRepMap->Find(Actor) : nullptr;

				if (FConnectionReplicationActorInfo* ActorInfo = Params.ConnectionManager.ActorInfoMap.Find(Actor))
				{
					if (ActorInfo->bDormantOnConnection)
					{
						Params.ConnectionManager.NotifyAddDormantDestructionInfo(Actor);
						ActorInfo->bDormantOnConnection = false;
						// Ideally, no actor info outside this list should be set to true, so we don't have to worry about resetting them.
						// However we could consider iterating through the actor map to reset all of them.
						ActorInfo->bGridSpatilization_AlreadyDormant = false;


						// add back to connection specific dormancy nodes
						// Try to make sure that we're using the stored actor location otherwise we'll end up adding them to nodes they weren't in before
						const FVector& ActorLocation = GlobalActorInfo != nullptr ? GlobalActorInfo->WorldLocation : Actor->GetActorLocation();
						const FActorCellInfo CellInfo = GetCellInfoForActor(Actor, ActorLocation, ActorInfo->GetCullDistance());
						static TArray<ULRG_Node_GridCell*> s_GatheredNodes;
						s_GatheredNodes.Empty(s_GatheredNodes.Max());
						GetGridNodesForActor(Actor, CellInfo, s_GatheredNodes);
						for (auto* Node : s_GatheredNodes)
						{
							if (UReplicationGraphNode_DormancyNode* DormancyNode = Node->GetDormancyNode())
							{
								// Only notify the connection node if this client was previously inside the cell.
								if (UReplicationGraphNode_ConnectionDormancyNode* ConnectionDormancyNode = DormancyNode->GetExistingConnectionNode(Params))
								{
									ConnectionDormancyNode->NotifyActorDormancyFlush(Actor);
								}
							}
						}

						NumActorsToRemove--;
						PrevDormantActorList.RemoveAtSwap(i--);
					}
					else if (ActorInfo->Channel == nullptr)
					{
						//Channel was closed before becoming dormant.  Remove from list
						UE_CLOG(RepGraph_Verify, LogReplicationGraph, Warning, TEXT("ULRG_Node_GridSpatial::GatherActorListsForConnection: Actor with null channel pointer in Connection's PrevDormantActorList, it is preferred that we remove actors from the PrevDormantActorList when we clear their channel."));
						ActorInfo->bGridSpatilization_AlreadyDormant = false;
						PrevDormantActorList.RemoveAtSwap(i--);
					}
				}
			}
		}
	}
#endif // WITH_SERVER_CODE
}

void ULRG_Node_GridSpatial::DebugCellChange(FActorRepListType& DynamicActor,
	const TMap<int64, ULRG_Node_GridCell*>& PreviousNodesMap, const TMap<int64, ULRG_Node_GridCell*>& NewNodesMap)
{
	FString EnterInfo;
	for(const auto& KeyValue : NewNodesMap)
	{
		if(!PreviousNodesMap.Contains(KeyValue.Key))
		{
			int x, y = 0;
			CalcGridCellIndex(KeyValue.Key, x, y);
			EnterInfo += FString::Printf(TEXT("[%d-%d]"), x, y);
		}
	}
	FString LeaveInfo;
	for(const auto& KeyValue : PreviousNodesMap)
	{
		if(!NewNodesMap.Contains(KeyValue.Key))
		{
			int x, y = 0;
			CalcGridCellIndex(KeyValue.Key, x, y);
			LeaveInfo += FString::Printf(TEXT("[%d-%d]"), x, y);
		}
	}
	if(!EnterInfo.IsEmpty() || !LeaveInfo.IsEmpty())
	{
		FString CellChangeDebugInfo;
		if(DynamicActor->GetDesc().IsEmpty())
		{
			CellChangeDebugInfo = FString::Printf(TEXT("%s:"), *DynamicActor->GetName());
		}
		else
		{
			CellChangeDebugInfo = FString::Printf(TEXT("%s:"), *DynamicActor->GetDesc());
		}
		for(const auto& KeyValue : PreviousNodesMap)
		{
			if(NewNodesMap.Contains(KeyValue.Key))
			{
				int x, y = 0;
				CalcGridCellIndex(KeyValue.Key, x, y);
				CellChangeDebugInfo += FString::Printf(TEXT("[%d-%d]"), x, y);
			}
		}
		if( !EnterInfo.IsEmpty() && !LeaveInfo.IsEmpty() ) {
			UE_LOG(LogReplicationGraph, Log, TEXT("%s+%s-%s"), *CellChangeDebugInfo, *EnterInfo, *LeaveInfo);
		}
		else if( !EnterInfo.IsEmpty() ) {
			UE_LOG(LogReplicationGraph, Log, TEXT("%s+%s"), *CellChangeDebugInfo, *EnterInfo);
		}
		else {
			UE_LOG(LogReplicationGraph, Log, TEXT("%s-%s"), *CellChangeDebugInfo, *LeaveInfo);
		}
	}
}

void ULRG_Node_GridSpatial::CalculateActorLocation(const FVector& Location)
{
	auto Coordinate = LocationToCoordinate(Location);
	TryCreateCell(Coordinate.X, Coordinate.Y);
}

void ULRG_Node_GridSpatial::UpdateLocationCell(ULineReplicationGraphComponent* Component)
{
	auto Coordinate = LocationToCoordinate(Component->GetWorldLocation());
	Component->UpdateLocationCell( TryCreateCell(Coordinate.X, Coordinate.Y) );
}

ULRG_Node_GridCell* ULRG_Node_GridSpatial::TryCreateCell(int32 X, int32 Y)
{
	const int32 Key = CalcGridCellKey(X, Y);
	auto Value = GridCellMap.Find(Key);
	if(Value && *Value)
	{
		return *Value;
	}
	ULRG_Node_GridCell* GridCell = CreateChildNode<ULRG_Node_GridCell>();
	GridCell->InitializeCell(this, X, Y);
	GridCellMap.Add(Key,GridCell);
	return GridCell;
}

ULRG_Node_GridCell* ULRG_Node_GridSpatial::GetCell(int32 X, int32 Y)
{
	int64 Key = CalcGridCellKey(X, Y);
	auto Value = GridCellMap.Find(CalcGridCellKey(X, Y));
	if(Value)
	{
		return *Value;
	}
	return nullptr;
}
 
int64 ULRG_Node_GridSpatial::LocationToId(const FVector& Location3D) const
{
	int32 X = static_cast<int32>( Location3D.X / CellSize );
	int32 Y = static_cast<int32>( Location3D.Y / CellSize );
	return FSpatialCoordinate::CoordinateToId(X, Y);
}

FSpatialCoordinate ULRG_Node_GridSpatial::LocationToCoordinate(const FVector& Location) const
{
	FSpatialCoordinate Coordinate;
	Coordinate.X = CalculateCoordinate( Location.X  );
	Coordinate.Y = CalculateCoordinate( Location.Y  );
	return Coordinate;
}

int32 ULRG_Node_GridSpatial::CalculateCoordinate(float LocationValue) const
{
	if(LocationValue >= 0)
	{
		return static_cast<int32>( LocationValue / CellSize );
	}
	return -static_cast<int32>( (-LocationValue + CellSize) / CellSize );
}